/*
 * Spinneret - Semi-Structured Database Engine
 * Copyright (c) 2009 Tom Bradford <spinneretdb.org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include <string.h>
#include "buffers/buffers.h"
#include "tx/locks.h"

/* Private Types and Variables ***********************************************/

// Forward declarations
typedef struct sdb_buffer_lock_t sdb_buffer_lock_t;
typedef struct sdb_buffer_mark_t sdb_buffer_mark_t;

/** Implementation type for Buffer Manager instances */
struct sdb_buffers_t {
    sdb_pager_t* pager;
};

/** Implementation type for Lock Reference instances */
struct sdb_buffer_lock_t {
    sdb_lock_t* lock;
    sdb_buffer_lock_t* next;
};

/** Implementation type for Bookmark instances */
struct sdb_buffer_mark_t {
    sdb_size_t pos;
    sdb_buffer_mark_t* next;
};

/** Implementation type for Buffer instances */
struct sdb_buffer_t {
    sdb_byte_t* data;

    sdb_size_t len;          // The length of this Buffer's data
    sdb_size_t limit;        // The limit of this Buffer's window
    sdb_size_t pos;          // The current read/write position

    sdb_int_t pinCount;      // How many processes have this buffer pinned
    sdb_int_t mruPasses;     // The number of misses on this page
    bool dirty;              // Has the page been modified since it was read?

    sdb_buffer_mark_t* marks; // Most recently marked location
    sdb_buffer_lock_t* locks; // Outstanding locks on this buffer
};

/* Private Functions *********************************************************/

static void private_buffer_clearMarks(sdb_buffer_t* buffer)
{
    while ( buffer->locks ) {
        sdb_buffer_lock_t* lock = buffer->locks;
        buffer->locks = lock->next;
        sdb_ptr_free(lock);
    }

    while ( buffer->marks ) {
        sdb_buffer_mark_t* mark = buffer->marks;
        buffer->marks = mark->next;
        sdb_ptr_free(mark);
    }
}

/*---------------------------------------------------------------------------*/

static void private_buffer_finalizer(sdb_buffer_t* buffer)
{
    private_buffer_clearMarks(buffer);
    sdb_ptr_free(buffer->data);
}

/* Public Functions **********************************************************/

sdb_buffers_t* sdb_buffers_new(sdb_mem_t* mem)
{
    return sdb_mem_malloc(mem, sizeof(sdb_buffers_t));
}

/*---------------------------------------------------------------------------*/

sdb_buffers_t* sdb_buffers_init(sdb_buffers_t* bufmgr, sdb_pager_t* pager)
{
    bufmgr->pager = pager;

    return bufmgr;
}

/*---------------------------------------------------------------------------*/

sdb_buffer_t* sdb_buffer_new(sdb_mem_t* mem)
{
    return sdb_mem_malloc(mem, sizeof(sdb_buffer_t));
}

/*---------------------------------------------------------------------------*/

sdb_buffer_t* sdb_buffer_init(sdb_buffer_t* buffer, sdb_size_t len)
{
    sdb_mem_t* mem = sdb_ptr_getMem(buffer);
    buffer->data = sdb_mem_malloc(mem, len);

    buffer->len = len;
    buffer->limit = len;
    buffer->pos = 0;
    buffer->pinCount = 0;
    buffer->mruPasses = 0;
    buffer->dirty = false;
    buffer->marks = NULL;
    buffer->locks = NULL;

    sdb_ptr_setDisposalFunction(buffer, private_buffer_finalizer);

    return buffer;
}

/*---------------------------------------------------------------------------*/

sdb_size_t sdb_buffer_avail(sdb_buffer_t* buffer)
{
    return (buffer->limit - buffer->pos);
}

/*---------------------------------------------------------------------------*/

sdb_size_t sdb_buffer_skip(sdb_buffer_t* buffer, sdb_size_t len)
{
    sdb_size_t avail = sdb_buffer_avail(buffer);
    if ( len > avail )
        len = avail;

    buffer->pos += len;

    return len;
}

/*---------------------------------------------------------------------------*/

sdb_size_t sdb_buffer_put(sdb_buffer_t* buffer, void* data, sdb_size_t len)
{
    sdb_size_t avail = sdb_buffer_avail(buffer);
    if ( len > avail )
        len = avail;

    memmove(buffer->data + buffer->pos, data, len);
    buffer->pos += len;

    return len;
}

/*---------------------------------------------------------------------------*/

sdb_size_t sdb_buffer_get(sdb_buffer_t* buffer, void* data, sdb_size_t len)
{
    sdb_size_t avail = sdb_buffer_avail(buffer);
    if ( len > avail )
        len = avail;

    memmove(data, buffer->data + buffer->pos, len);
    buffer->pos += len;

    return len;
}

/*---------------------------------------------------------------------------*/

sdb_size_t sdb_buffer_copy(sdb_buffer_t* buffer, sdb_buffer_t* target)
{
    sdb_size_t len = sdb_buffer_put(target, buffer->data + buffer->pos,
                                    sdb_buffer_avail(buffer));
    buffer->pos += len;

    return len;
}

/*---------------------------------------------------------------------------*/

void sdb_buffer_pushMark(sdb_buffer_t* buffer)
{
    sdb_mem_t* mem = sdb_ptr_getMem(buffer);
    sdb_buffer_mark_t* mark = sdb_mem_malloc(mem, sizeof(sdb_buffer_mark_t));

    mark->pos = buffer->pos;
    if ( buffer->marks )
        mark->next = buffer->marks;

    buffer->marks = mark;
}

/*---------------------------------------------------------------------------*/

void sdb_buffer_popMark(sdb_buffer_t* buffer)
{
    if ( buffer->marks ) {
        sdb_buffer_mark_t* mark = buffer->marks;
        buffer->pos = mark->pos;
        buffer->marks = mark->next;
        sdb_ptr_free(mark);
    }
    else
        buffer->pos = 0;
}

/*---------------------------------------------------------------------------*/

void sdb_buffer_rewind(sdb_buffer_t* buffer)
{
    buffer->pos = 0;
}

/*---------------------------------------------------------------------------*/

void sdb_buffer_flip(sdb_buffer_t* buffer)
{
    private_buffer_clearMarks(buffer);
    buffer->limit = buffer->pos;
    buffer->pos = 0;
}

/*---------------------------------------------------------------------------*/

void* sdb_buffer_data(sdb_buffer_t* buffer)
{
    return buffer->data;
}

/*---------------------------------------------------------------------------*/

void* sdb_buffer_pos(sdb_buffer_t* buffer)
{
    return buffer->data + buffer->pos;
}

/*---------------------------------------------------------------------------*/

sdb_size_t sdb_buffer_len(sdb_buffer_t* buffer)
{
    return buffer->len;
}

/*---------------------------------------------------------------------------*/

sdb_size_t sdb_buffer_limit(sdb_buffer_t* buffer)
{
    return buffer->limit;
}
